﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  public partial class PanelBase
  {
    internal class PanelSelectionAndFocusManager
    {
      #region Constructor

      public PanelSelectionAndFocusManager( PanelBase parentPanel )
      {
        m_panel = parentPanel;
      }

      #endregion

      #region Internal Methods

      internal void SetSelectedDataPath( DataPath dataPath )
      {
        m_focusOrSelectDataPath = dataPath;
      }

      internal bool HasSelectedDataPath()
      {
        return ( m_focusOrSelectDataPath != null );
      }

      internal bool IsSelectedDataPath( DataPath dataPath )
      {
        return object.Equals( m_focusOrSelectDataPath, dataPath );
      }

      internal void SetSelectedDataPathNavigationInfo( NavigationInfo navigationInfo )
      {
        m_focusOrSelectDataPathNavigationInfo = navigationInfo;
      }

      internal void ClearDataPathToSelectAndFocus()
      {
        m_focusOrSelectDataPath = null;
        m_focusOrSelectDataPathNavigationInfo = null;
      }

      // This method will return the focused listbox container in the panel.
      // If no listbox container is focused, the method returns null.
      internal ListBoxContainer FindFocusedContainer()
      {
        // Get the focus element from the focus manager and try to cast it as a ListBoxContainer
#if SILVERLIGHT
        ListBoxContainer focusedContainer = FocusManager.GetFocusedElement() as ListBoxContainer;
#else
        ListBoxContainer focusedContainer = FocusManager.GetFocusedElement( this.m_panel ) as ListBoxContainer;

        if( focusedContainer == null )
        {
          foreach( ListBoxContainer container in m_panel.Children )
          {
            if( container.IsFocused )
            {
              focusedContainer = container;
              break;
            }
          }
        }
#endif

        // Make sure the focused container is a member of the parent listbox
        if( focusedContainer != null )
        {
          if( focusedContainer.ParentListBox != m_panel.ParentListBox )
            return null;
        }

        return focusedContainer;
      }

      internal DataPath GetFocusedOrCurrentDataPath()
      {
        // Use the FocusedContainer's DataPath for navigation as default
        ListBoxContainer focusedContainer = this.FindFocusedContainer();

        if( focusedContainer != null )
          return focusedContainer.DataPath;

        // Unable to find a focused container, use the CurrentDataPath
        return m_panel.ParentListBox.CurrentDataPath;
      }

      internal void BeginSelectAndFocusDataPath()
      {
        if( m_focusOrSelectContainerOperation == null )
        {
          m_focusOrSelectContainerOperation = m_panel.Dispatcher.BeginInvoke( new Action( this.SelectAndFocusDataPath ) );
        }
      }

      internal void SelectAndFocusDataPath()
      {
        m_focusOrSelectContainerOperation = null;

        DataPath dataPath = m_focusOrSelectDataPath;

        // The target container cannot be focused until its DataPath is loaded.
        if( ( dataPath != null ) && ( !dataPath.LastChild.IsLoaded ) )
          return;

        NavigationInfo navigationInfo = m_focusOrSelectDataPathNavigationInfo;

        this.ClearDataPathToSelectAndFocus();

        if( dataPath != null )
        {
          ListBoxContainer container = m_panel.LayoutManager.GetContainerFromPath( dataPath, true );

          if( container != null )
          {
            bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

            // Update the selection.
            if( ( isShiftDown ) || ( navigationInfo != null && !navigationInfo.IsCtrlDown ) )
            {
              m_panel.ParentListBox.UpdateSelection( container, SelectionAction.Navigation );
            }

            container.Focus();
            m_panel.ProcessRecyclingWaitingList();
          }
        }
      }

      internal void BeginFocusOrSelectContainerAfterScrollForcedForNavigation( NavigationInfo navigationInfo )
      {
        if( m_focusOrSelectContainerAfterScrollForcedForNavigationOperation == null )
        {
          m_focusOrSelectContainerAfterScrollForcedForNavigationOperation =
            m_panel.Dispatcher.BeginInvoke(
              new Action<NavigationInfo>( this.FocusOrSelectContainerAfterScrollForcedForNavigation ),
              navigationInfo );
        }
      }

      internal void FocusOrSelectContainerAfterScrollForcedForNavigation( NavigationInfo navigationInfo )
      {
        m_focusOrSelectContainerAfterScrollForcedForNavigationOperation = null;

        // NavigationInfo can be null when this call is issued from BringItemIntoView
        if( navigationInfo == null )
          return;

        if( navigationInfo.ForcedBringIntoView )
        {
          DataPath focusedOrCurrentDataPath = this.GetFocusedOrCurrentDataPath();

          bool selectAndFocusOrScrollToDataPathDone =
            m_panel.SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( focusedOrCurrentDataPath, navigationInfo );

          if( !selectAndFocusOrScrollToDataPathDone )
            Debug.Assert( false, "Unable to process the keyboard navigation after the current Item was brought into view" );

          return;
        }

        //Get the path to set as current
        DataPath newCurrentOrFocusedDataPath = null;

        // We scrolled to force the last or first container to become
        // into view, get it according to scrolling direction
        newCurrentOrFocusedDataPath = ( navigationInfo.IsForwardScrolling )
          ? m_panel.LayoutManager.GetLastFullyVisibleDataPath()
          : m_panel.LayoutManager.GetFirstFullyVisibleDataPath();

        // We scrolled, but still no fully visible container
        if( newCurrentOrFocusedDataPath == null )
        {
          // Get the last
          int containerDescriptionCount = m_panel.LayoutManager.ContainerDescriptions.Count;

          if( containerDescriptionCount == 0 )
          {
            Debug.Assert( false, "We scrolled to force a container to become visible, but we didn't find any item when scrolling completed" );
            return;
          }

          // Get the first or last data path in m_containerDescriptions
          if( navigationInfo.IsForwardScrolling )
          {
            newCurrentOrFocusedDataPath = m_panel.LayoutManager.ContainerDescriptions[ containerDescriptionCount - 1 ].DataPath;
          }
          else
          {
            newCurrentOrFocusedDataPath = m_panel.LayoutManager.ContainerDescriptions[ 0 ].DataPath;
          }

          Debug.Assert( newCurrentOrFocusedDataPath != null, "We scrolled to force a container to become visible, but we didn't find any item when scrolling completed" );
        }

        if( newCurrentOrFocusedDataPath.LastChild.IsLoaded )
        {
          this.ClearDataPathToSelectAndFocus();

          ListBoxContainer container = m_panel.LayoutManager.GetContainerFromPath( newCurrentOrFocusedDataPath, true );

          if( container != null )
          {
            ListBox listBox = m_panel.ParentListBox;

            bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;
            bool isCtrlDown = navigationInfo.IsCtrlDown;

            if( !isCtrlDown || ( isCtrlDown && isShiftDown ) )
              listBox.UpdateSelection( container, SelectionAction.Navigation );

            listBox.CurrentDataPath = newCurrentOrFocusedDataPath;

            container.Focus();
            m_panel.ProcessRecyclingWaitingList();
          }
          else
          {
            Debug.Assert( false, "We scrolled so we could find the desired container" );
          }
        }
        else
        {
          // Force this method to be dispatched to be sure to call it after the scroll operation is completed
          m_focusOrSelectContainerAfterScrollForcedForNavigationOperation =
            m_panel.Dispatcher.BeginInvoke(
              new Action<NavigationInfo>( this.FocusOrSelectContainerAfterScrollForcedForNavigation ),
              navigationInfo );
        }
      }

      #endregion

      #region Private Field

      private PanelBase m_panel;

      private DispatcherOperation m_focusOrSelectContainerAfterScrollForcedForNavigationOperation;
      private DispatcherOperation m_focusOrSelectContainerOperation;
      private DataPath m_focusOrSelectDataPath;
      private NavigationInfo m_focusOrSelectDataPathNavigationInfo;

      #endregion
    }
  }
}
